```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket技术详解 - 双向通信的革命</title>
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', 'Helvetica Neue', Arial, sans-serif;
            color: #2d3748;
            line-height: 1.6;
            background-color: #f8fafc;
        }
        h1, h2, h3, h4 {
            font-family: 'Noto Serif SC', serif;
            font-weight: 600;
        }
        .hero {
            background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
            color: white;
        }
        .code-block {
            background-color: #1e293b;
            color: #f8fafc;
            border-radius: 0.5rem;
            overflow-x: auto;
        }
        .card {
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
        }
        .table-container {
            overflow-x: auto;
        }
        table {
            min-width: 100%;
            border-collapse: separate;
            border-spacing: 0;
        }
        th, td {
            padding: 0.75rem;
            text-align: left;
            border-bottom: 1px solid #e2e8f0;
        }
        th {
            background-color: #f1f5f9;
            font-weight: 600;
        }
        .annotation {
            background-color: #f0fdf4;
            border-left: 4px solid #10b981;
            padding: 1rem;
            margin: 1rem 0;
        }
        .diagram-container {
            background-color: white;
            padding: 1.5rem;
            border-radius: 0.5rem;
            box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
        }
    </style>
</head>
<body>
    <!-- Hero Section -->
    <div class="hero py-20 px-4 sm:px-6 lg:px-8 text-center">
        <div class="max-w-4xl mx-auto">
            <h1 class="text-4xl md:text-5xl font-bold mb-6">WebSocket技术详解</h1>
            <p class="text-xl md:text-2xl mb-8 opacity-90">打破HTTP限制，实现真正的全双工通信</p>
            <div class="flex justify-center space-x-4">
                <a href="#intro" class="px-6 py-3 bg-white text-blue-600 font-medium rounded-full hover:bg-blue-50 transition duration-300">
                    <i class="fas fa-book mr-2"></i>开始阅读
                </a>
                <a href="#demo" class="px-6 py-3 border border-white text-white font-medium rounded-full hover:bg-white hover:bg-opacity-10 transition duration-300">
                    <i class="fas fa-code mr-2"></i>代码示例
                </a>
            </div>
        </div>
    </div>

    <!-- Main Content -->
    <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
        <!-- Introduction -->
        <section id="intro" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-lightbulb text-yellow-500 mr-2"></i>引言</h2>
            <div class="bg-white rounded-xl shadow-md p-6 mb-8">
                <p class="mb-4 text-lg text-gray-700">HTTP协议是一种单向的网络协议，由客户端发起，在建立连接后，它只允许客户端向服务器发出请求，服务器才能返回相应的数据。</p>
                
                <div class="diagram-container my-6">
                    <div class="mermaid">
                        sequenceDiagram
                            participant Client
                            participant Server
                            Client->>Server: HTTP Request
                            Server->>Client: HTTP Response
                            Note right of Server: 服务器无法主动推送数据
                    </div>
                </div>
                
                <p class="mb-4 text-gray-700">同时HTTP协议有一个缺陷，通信只能由客户端发起。这种单向请求的特点，注定了如果服务器有状态变化，是无法主动通知客户端的。如果要及时获取服务器的最新数据，只能由客户端发起轮训，如果服务端在一段时间内数据没有变化，而客户端有一直在轮训就会造成资源浪费。</p>
                
                <div class="annotation">
                    <p class="font-medium mb-2">场景示例：</p>
                    <p>模拟一个客户端A给客户端B发送消息，客户端A把消息发送给服务器，此时客户端B怎么知道客户端A给它发消息了呢？只能通过轮训。</p>
                </div>
                
                <div class="diagram-container my-6">
                    <div class="mermaid">
                        sequenceDiagram
                            participant ClientA
                            participant Server
                            participant ClientB
                            ClientA->>Server: 发送消息
                            loop 轮询
                                ClientB->>Server: 有新消息吗？
                                Server->>ClientB: 无/有
                            end
                    </div>
                </div>
                
                <p class="text-gray-700">综上所述HTTP的协议并不适合做双向通讯，所以需要一个新的协议来实现客户端和服务端之间的双向通讯。此时WebSocket就诞生了。</p>
            </div>
        </section>

        <!-- WebSocket Introduction -->
        <section id="websocket-intro" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-plug text-blue-500 mr-2"></i>WebSocket简介</h2>
            <div class="bg-white rounded-xl shadow-md p-6 mb-8">
                <p class="mb-4 text-gray-700">WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行<strong class="text-blue-600">全双工通讯</strong>的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。</p>
                
                <p class="mb-6 text-gray-700">在 WebSocket API 中，浏览器和服务器只需要完成一次握手，两者之间就直接可以创建持久性的连接，并进行双向数据传输。</p>
                
                <div class="diagram-container my-6">
                    <div class="mermaid">
                        sequenceDiagram
                            participant Client
                            participant Server
                            Client->>Server: WebSocket Handshake
                            Server->>Client: 101 Switching Protocols
                            Note over Client,Server: 持久连接建立
                            Client->>Server: 双向数据传输
                            Server->>Client: 双向数据传输
                    </div>
                </div>
                
                <p class="mb-4 text-gray-700">现在，很多网站为了实现推送技术，所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔（如每1秒），由浏览器对服务器发出HTTP请求，然后由服务器返回最新的数据给客户端的浏览器。</p>
                
                <div class="flex flex-col md:flex-row gap-6 my-8">
                    <div class="card bg-white p-6 rounded-xl shadow-md flex-1">
                        <div class="flex items-center mb-4">
                            <div class="bg-red-100 p-3 rounded-full mr-4">
                                <i class="fas fa-sync-alt text-red-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">轮询问题</h3>
                        </div>
                        <ul class="space-y-2 text-gray-700">
                            <li class="flex items-start">
                                <i class="fas fa-times-circle text-red-400 mr-2 mt-1"></i>
                                <span>需要不断发起HTTP请求</span>
                            </li>
                            <li class="flex items-start">
                                <i class="fas fa-times-circle text-red-400 mr-2 mt-1"></i>
                                <span>HTTP头部信息冗余</span>
                            </li>
                            <li class="flex items-start">
                                <i class="fas fa-times-circle text-red-400 mr-2 mt-1"></i>
                                <span>服务器资源和带宽浪费</span>
                            </li>
                        </ul>
                    </div>
                    
                    <div class="card bg-white p-6 rounded-xl shadow-md flex-1">
                        <div class="flex items-center mb-4">
                            <div class="bg-green-100 p-3 rounded-full mr-4">
                                <i class="fas fa-bolt text-green-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">WebSocket优势</h3>
                        </div>
                        <ul class="space-y-2 text-gray-700">
                            <li class="flex items-start">
                                <i class="fas fa-check-circle text-green-400 mr-2 mt-1"></i>
                                <span>全双工通信</span>
                            </li>
                            <li class="flex items-start">
                                <i class="fas fa-check-circle text-green-400 mr-2 mt-1"></i>
                                <span>一次握手持久连接</span>
                            </li>
                            <li class="flex items-start">
                                <i class="fas fa-check-circle text-green-400 mr-2 mt-1"></i>
                                <span>节省服务器资源</span>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </section>

        <!-- Connection Types -->
        <section id="connection-types" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-exchange-alt text-purple-500 mr-2"></i>长连接与短连接</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                <!-- Short Connection -->
                <div class="card bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-purple-100 p-4">
                        <h3 class="text-xl font-semibold text-purple-800">短连接</h3>
                    </div>
                    <div class="p-6">
                        <p class="mb-4 text-gray-700">客户端和服务器每进行一次HTTP操作，就建立一次连接，任务结束就中断连接。</p>
                        <p class="mb-4 text-gray-700">当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源（如JavaScript文件、图像文件、CSS文件等），每遇到这样一个Web资源，浏览器就会重新建立一个HTTP会话。</p>
                        
                        <div class="bg-gray-50 p-4 rounded-lg mt-4">
                            <p class="font-medium text-gray-700">短连接过程：</p>
                            <p class="text-gray-600">建立连接 → 数据传输 → 关闭连接...建立连接 → 数据传输 → 关闭连接</p>
                        </div>
                    </div>
                </div>
                
                <!-- Long Connection -->
                <div class="card bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-indigo-100 p-4">
                        <h3 class="text-xl font-semibold text-indigo-800">长连接</h3>
                    </div>
                    <div class="p-6">
                        <p class="mb-4 text-gray-700">在使用长连接的情况下，当一个网页打开完成后，客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭，客户端再次访问这个服务器时，会继续使用这一条已经建立的连接。</p>
                        
                        <div class="bg-gray-50 p-4 rounded-lg mt-4">
                            <p class="font-medium text-gray-700">长连接过程：</p>
                            <p class="text-gray-600">建立连接 → 数据传输...（保持连接）...数据传输 → 关闭连接</p>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- WebSocket Server Setup -->
        <section id="server-setup" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-server text-amber-500 mr-2"></i>搭建WebSocket服务器</h2>
            
            <div class="space-y-8">
                <!-- Step 1 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-amber-100 p-4 flex items-center">
                        <div class="bg-amber-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">1</div>
                        <h3 class="text-xl font-semibold text-amber-800">导入依赖</h3>
                    </div>
                    <div class="p-6">
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.yeauty&lt;/groupId&gt;
    &lt;artifactId&gt;netty-websocket-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;0.12.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
                        </div>
                    </div>
                </div>
                
                <!-- Step 2 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-amber-100 p-4 flex items-center">
                        <div class="bg-amber-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">2</div>
                        <h3 class="text-xl font-semibold text-amber-800">启动WebSocket功能</h3>
                    </div>
                    <div class="p-6">
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-java">@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        System.out.println("WebSocket启动");
        return new ServerEndpointExporter();
    }
}</code></pre>
                        </div>
                    </div>
                </div>
                
                <!-- Step 3 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-amber-100 p-4 flex items-center">
                        <div class="bg-amber-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">3</div>
                        <h3 class="text-xl font-semibold text-amber-800">WebSocket配置类</h3>
                    </div>
                    <div class="p-6">
                        <div class="annotation mb-6">
                            <p><strong>注意：</strong>@ServerEndpoint 每个客户端连接，都会生成一个实例，所以需要有一个单独的bean来管理session。</p>
                        </div>
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-java">@ServerEndpoint(host = "${websocket.host}", path = "${websocket.path}", port = "${websocket.port}")
@Slf4j
public class ShopWebSocket {

    @BeforeHandshake
    public void handshake(Session session, HttpHeaders headers,
                          @RequestParam String req,
                          @RequestParam MultiValueMap reqMap) {
        // 把客户端信息保存到session中
        String token = headers.get("token");
        log.debug("新客户端连接:"+token);
        session.setAttribute("token", token);
    }

    @OnOpen
    public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, 
                      @RequestParam MultiValueMap reqMap, @PathVariable String arg, 
                      @PathVariable Map pathMap) {
        String token = (String) session.getAttribute("token");
        log.debug("客户端【" + token + "】连接成功");
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        String token = session.getAttribute("token");
        log.debug("客户端【" + token + "】关闭");
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        throwable.printStackTrace();
    }

    @OnMessage
    public void onMessage(Session session, String message) {
        log.debug("客户端【" + session.getAttribute("token") + "】发送消息:" + message);
        session.sendText("Hello Netty!");
    }

    @OnBinary
    public void onBinary(Session session, byte[] bytes) {
        for (byte b : bytes) {
            log.debug(""+b);
        }
        session.sendBinary(bytes);
    }

    @OnEvent
    public void onEvent(Session session, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            switch (idleStateEvent.state()) {
                case READER_IDLE:
                    log.debug("read idle");
                    break;
                case WRITER_IDLE:
                    log.debug("write idle");
                    break;
                case ALL_IDLE:
                    log.debug("all idle");
                    break;
                default:
                    break;
            }
        }
    }
}</code></pre>
                        </div>
                    </div>
                </div>
                
                <!-- Step 4 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-amber-100 p-4 flex items-center">
                        <div class="bg-amber-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">4</div>
                        <h3 class="text-xl font-semibold text-amber-800">yml配置</h3>
                    </div>
                    <div class="p-6">
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-yaml">websocket:
  host: 127.0.0.1
  path: /ws
  port: 8005</code></pre>
                        </div>
                    </div>
                </div>
                
                <!-- Step 5 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-amber-100 p-4 flex items-center">
                        <div class="bg-amber-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">5</div>
                        <h3 class="text-xl font-semibold text-amber-800">启动项目</h3>
                    </div>
                    <div class="p-6">
                        <div class="bg-gray-50 p-4 rounded-lg mb-4">
                            <p class="font-medium">客户端通过 <code class="bg-gray-200 px-2 py-1 rounded">ws://127.0.0.1:8005/ws</code> 就可以测试连接</p>
                        </div>
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653192491021-923a3752-11e5-4d2b-9637-1a808a1ef01d.png" alt="WebSocket测试" class="rounded-lg shadow border border-gray-200">
                    </div>
                </div>
            </div>
        </section>

        <!-- WebSocket Client -->
        <section id="client" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-laptop-code text-blue-500 mr-2"></i>WebSocket客户端连接</h2>
            
            <div class="bg-white rounded-xl shadow-md p-6">
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                    <div>
                        <h3 class="text-xl font-semibold mb-4">1. 创建WebSocket连接</h3>
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653192661980-0c340e40-ed34-4845-9df0-217e12e56d4b.png" alt="创建连接" class="rounded-lg shadow border border-gray-200 mb-2">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653192682002-9f95142c-1e62-466f-9199-e7f962020ead.png" alt="连接面板" class="rounded-lg shadow border border-gray-200">
                    </div>
                    
                    <div>
                        <h3 class="text-xl font-semibold mb-4">2. 输入WebSocket信息连接服务端</h3>
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653192762251-5e412524-9221-41c8-91e3-2559e3b837b3.png" alt="连接信息" class="rounded-lg shadow border border-gray-200">
                    </div>
                    
                    <div>
                        <h3 class="text-xl font-semibold mb-4">3. 测试结果</h3>
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653192860242-dc92c9e7-3454-4587-8ef5-27f4a34361a7.png" alt="测试结果" class="rounded-lg shadow border border-gray-200">
                    </div>
                </div>
            </div>
        </section>

        <!-- WebSocket Annotations -->
        <section id="annotations" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-tags text-green-500 mr-2"></i>WebSocket注解说明</h2>
            
            <div class="bg-white rounded-xl shadow-md p-6 mb-6">
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div>
                        <h3 class="text-xl font-semibold mb-4">核心注解</h3>
                        <ul class="space-y-4">
                            <li>
                                <p class="font-medium">@ServerEndpoint</p>
                                <p class="text-gray-700">当ServerEndpointExporter类通过Spring配置进行声明并被使用，它将会去扫描带有@ServerEndpoint注解的类被注解的类将被注册成为一个WebSocket端点</p>
                            </li>
                            <li>
                                <p class="font-medium">@BeforeHandshake</p>
                                <p class="text-gray-700">当有新的连接进入时，对该方法进行回调 注入参数的类型:Session、HttpHeaders...</p>
                            </li>
                            <li>
                                <p class="font-medium">@OnOpen</p>
                                <p class="text-gray-700">当有新的WebSocket连接完成时，对该方法进行回调 注入参数的类型:Session、HttpHeaders...</p>
                            </li>
                            <li>
                                <p class="font-medium">@OnClose</p>
                                <p class="text-gray-700">当有WebSocket连接关闭时，对该方法进行回调 注入参数的类型:Session</p>
                            </li>
                            <li>
                                <p class="font-medium">@OnError</p>
                                <p class="text-gray-700">当有WebSocket抛出异常时，对该方法进行回调 注入参数的类型:Session、Throwable</p>
                            </li>
                            <li>
                                <p class="font-medium">@OnMessage</p>
                                <p class="text-gray-700">当接收到字符串消息时，对该方法进行回调 注入参数的类型:Session、String</p>
                            </li>
                        </ul>
                    </div>
                    
                    <div>
                        <h3 class="text-xl font-semibold mb-4">其他注解</h3>
                        <ul class="space-y-4">
                            <li>
                                <p class="font-medium">@OnBinary</p>
                                <p class="text-gray-700">当接收到二进制消息时，对该方法进行回调 注入参数的类型:Session、byte[]</p>
                            </li>
                            <li>
                                <p class="font-medium">@OnEvent</p>
                                <p class="text-gray-700">当接收到Netty的事件时，对该方法进行回调 注入参数的类型:Session、Object</p>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
            
            <div class="bg-white rounded-xl shadow-md overflow-hidden">
                <div class="bg-green-100 p-4">
                    <h3 class="text-xl font-semibold text-green-800">@ServerEndpoint属性说明</h3>
                </div>
                <div class="p-6">
                    <div class="table-container">
                        <table class="w-full">
                            <thead>
                                <tr>
                                    <th>属性</th>
                                    <th>默认值</th>
                                    <th>说明</th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr>
                                    <td>path</td>
                                    <td>"/"</td>
                                    <td>WebSocket的path,也可以用value来设置</td>
                                </tr>
                                <tr>
                                    <td>host</td>
                                    <td>"0.0.0.0"</td>
                                    <td>WebSocket的host,"0.0.0.0"即是所有本地地址</td>
                                </tr>
                                <tr>
                                    <td>port</td>
                                    <td>80</td>
                                    <td>WebSocket绑定端口号。如果为0，则使用随机端口</td>
                                </tr>
                                <tr>
                                    <td>bossLoopGroupThreads</td>
                                    <td>0</td>
                                    <td>bossEventLoopGroup的线程数</td>
                                </tr>
                                <tr>
                                    <td>workerLoopGroupThreads</td>
                                    <td>0</td>
                                    <td>workerEventLoopGroup的线程数</td>
                                </tr>
                                <tr>
                                    <td>useCompressionHandler</td>
                                    <td>false</td>
                                    <td>是否添加WebSocketServerCompressionHandler到pipeline</td>
                                </tr>
                                <tr>
                                    <td>maxFramePayloadLength</td>
                                    <td>65536</td>
                                    <td>最大允许帧载荷长度</td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </section>

        <!-- Chat Implementation -->
        <section id="chat" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-comments text-indigo-500 mr-2"></i>实现聊天功能</h2>
            
            <div class="space-y-8">
                <!-- Step 1 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-indigo-100 p-4 flex items-center">
                        <div class="bg-indigo-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">1</div>
                        <h3 class="text-xl font-semibold text-indigo-800">创建session管理器</h3>
                    </div>
                    <div class="p-6">
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-java">@Component
public class SessionManager {

    // map用来保在线用户
    private Map&lt;String, Session&gt; sessionMap = new ConcurrentHashMap&lt;&gt;();

    public void add(String name, Session session) {
        sessionMap.put(name, session);
    }

    public Session get(String name){
        return sessionMap.get(name);
    }

    public void remove(String token){
        sessionMap.remove(token);
    }

    public int size(){
        return sessionMap.size();
    }
}</code></pre>
                        </div>
                    </div>
                </div>
                
                <!-- Step 2 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-indigo-100 p-4 flex items-center">
                        <div class="bg-indigo-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">2</div>
                        <h3 class="text-xl font-semibold text-indigo-800">封装消息对象</h3>
                    </div>
                    <div class="p-6">
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-java">@Data
public class MsgVo {

    // 接收人
    private String[] toUser;
    
    //消息内容
    private String text;
}</code></pre>
                        </div>
                    </div>
                </div>
                
                <!-- Step 3 -->
                <div class="bg-white rounded-xl shadow-md overflow-hidden">
                    <div class="bg-indigo-100 p-4 flex items-center">
                        <div class="bg-indigo-500 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">3</div>
                        <h3 class="text-xl font-semibold text-indigo-800">WebSocketHandler</h3>
                    </div>
                    <div class="p-6">
                        <div class="code-block p-4 rounded-lg">
                            <pre><code class="language-java">@ServerEndpoint(host = "${websocket.host}", path = "${websocket.path}", port = "${websocket.port}")
@Slf4j
public class ShopWebSocketMsg {

    @Autowired
    private SessionManager sessionManager;

    @BeforeHandshake
    public void handshake(Session session, HttpHeaders headers) {
        // 请求头中携带登录用户信息
        session.setAttribute("token", headers.get("token"));
    }

    @OnOpen
    public void onOpen(Session session, HttpHeaders headers) {
        String token = (String) session.getAttribute("token");
        System.out.println("客户端【" + token + "】连接成功");
        // 把每次连接的客户端保存到sessionManager中
        sessionManager.add(token, session);
        System.out.println("在线人数:" + sessionManager.size());
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        String token = session.getAttribute("token");
        System.out.println("客户端【" + token + "】关闭");
        // 客户但下线后从sessionManager中删除
        sessionManager.remove(token);
        System.out.println("在线人数:" + sessionManager.size());
    }

    @OnMessage
    public void onMessage(Session session, String msg) {
        System.out.println("客户端【" + session.getAttribute("token") + "】发送消息:" + msg);
        System.out.println("sessionMap:"+sessionManager);
        // 1.客户端发送的消息转成对象
        MsgVo msgVo = JSON.parseObject(msg, MsgVo.class);

        // 2.获取消息接收的人
        String[] toUser = msgVo.getToUser();

        for(int i=0;i&lt;toUser.length;i++){
        // 3.从sessionMap找接收人的session
            Session toSession = sessionManager.get(toUser[i]);
            if (toSession != null) {
                toSession.sendText(msgVo.getText());
            } else {
                session.sendText(toUser[i] + " 不在线");
            }
        }
    }
}</code></pre>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Gateway Routing -->
        <section id="gateway" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-route text-purple-500 mr-2"></i>Gateway 路由WebSocket请求</h2>
            
            <div class="bg-white rounded-xl shadow-md p-6">
                <div class="code-block p-4 rounded-lg">
                    <pre><code class="language-yaml">spring:
  cloud:
    gateway:
      routes:
        - id: shop-websocket
          uri: ws://localhost:8005
          predicates:
            - Path=/ws/**</code></pre>
                </div>
            </div>
        </section>

        <!-- Uni-app Implementation -->
        <section id="uniapp" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fab fa-weixin text-blue-400 mr-2"></i>uni-app封装websocket</h2>
            
            <div class="bg-white rounded-xl shadow-md p-6">
                <div class="code-block p-4 rounded-lg mb-6">
                    <pre><code class="language-javascript">// socket主要对象
let ws_url = "ws://127.0.0.1:80/ws";
export default {
  init: () => {
    uni.connectSocket({
        url: ws_url,
        header: {
            'content-type': 'application/json',
            "token":"uni-app"
        },
        method: 'GET'
    });
  },
  send: (data) => {
    uni.sendSocketMessage({
        data: JSON.stringify(data)
    });
  },
  onMessage: (message) => {
    uni.onSocketMessage(function (res) {
        message(res);
    });
  },
  close: () => {
    uni.closeSocket();
  },
  error:() =>{
    uni.onSocketError(function (res) {
        console.log('WebSocket连接打开失败，请检查！');
    });
  }
}</code></pre>
                </div>
                
                <div class="code-block p-4 rounded-lg">
                    <pre><code class="language-javascript">// 引入
import websocket from '@/request/websocket.js'

// 初始化
websocket.init();

// 发送消息
websocket.send(msgXxx);

// 监听webosocket错误
websocket.error();

// 监听消息
websocket.onMessage(function(resp){
    console.log(resp);
});</code></pre>
                </div>
            </div>
        </section>

        <!-- Binary Data Transfer -->
        <section id="binary" class="mb-16">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2"><i class="fas fa-file-binary text-gray-600 mr-2"></i>WebSocket传递二进制数据</h2>
            
            <div class="bg-white rounded-xl shadow-md p-6">
                <p class="mb-6 text-gray-700">实现是这样的先把二进制数组转成byteArray，然后把byteArray用Base64编码得到一个字符串，然后进行传递。服务端收到字符串后先进行Base64解码得到一个byteArray，然后把byteArray写入到流中即可。</p>
                
                <div class="space-y-8">
                    <!-- Step 1 -->
                    <div class="bg-white rounded-xl shadow-md overflow-hidden">
                        <div class="bg-gray-100 p-4 flex items-center">
                            <div class="bg-gray-600 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">1</div>
                            <h3 class="text-xl font-semibold text-gray-800">定义Base64工具类</h3>
                        </div>
                        <div class="p-6">
                            <div class="code-block p-4 rounded-lg">
                                <pre><code class="language-java">public class Base64Utils {

    static BASE64Encoder encoder = new sun.misc.BASE64Encoder();
    static BASE64Decoder decoder = new sun.misc.BASE64Decoder();

    public static String encodeImage(String url) {
        File file = new File(url);
        ByteArrayOutputStream baos = null;
        try {
            BufferedImage bi = ImageIO.read(file);
            baos = new ByteArrayOutputStream();
            ImageIO.write(bi, "jpg", baos);
            byte[] bytes = baos.toByteArray();
            return encoder.encodeBuffer(bytes).trim();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static byte[] decodeByte(String data) {
        try {
            return decoder.decodeBuffer(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}</code></pre>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Step 2 -->
                    <div class="bg-white rounded-xl shadow-md overflow-hidden">
                        <div class="bg-gray-100 p-4 flex items-center">
                            <div class="bg-gray-600 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">2</div>
                            <h3 class="text-xl font-semibold text-gray-800">设置netty解析数组大小</h3>
                        </div>
                        <div class="p-6">
                            <div class="code-block p-4 rounded-lg">
                                <pre><code class="language-java">@ServerEndpoint(maxFramePayloadLength =Integer.MAX_VALUE) // 默认是65536</code></pre>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Step 3 -->
                    <div class="bg-white rounded-xl shadow-md overflow-hidden">
                        <div class="bg-gray-100 p-4 flex items-center">
                            <div class="bg-gray-600 text-white font-bold rounded-full h-8 w-8 flex items-center justify-center mr-4">3</div>
                            <h3 class="text-xl font-semibold text-gray-800">传递二进制数据</h3>
                        </div>
                        <div class="p-6">
                            <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653215334303-7c7519ec-95b1-4504-a0d4-fd6d5a2fd8e2.png" alt="二进制传递" class="rounded-lg shadow border border-gray-200 mb-4">
                            <div class="code-block p-4 rounded-lg">
                                <pre><code class="language-java">@OnBinary
public void onBinary(Session session, byte[] bytes) {
    log.debug("onBinary:{}",bytes.length);
    session.sendBinary(bytes);
}</code></pre>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>

    <!-- Footer -->
    <footer class="bg-gray-900 text-gray-200 py-8">
        <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
            <div class="flex flex-col items-center">
                <div class="text-xl font-semibold mb-4">技术小馆</div>
                <a href="http://www.yuque.com/jtostring" class="text-blue-300 hover:text-white transition duration-300">
                    <i class="fas fa-globe mr-2"></i>http://www.yuque.com/jtostring
                </a>
            </div>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```